﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace UserInterface
{
    [Serializable()]
    public class CrossingType1 : Crossing
    {
        //how long it has been running
        int intervalRunning = 0;
        //how long we want it to run
        int intervalToRun;

        bool first = true;

        int indexGreen = 0;
        int indexRed = 3;
        Random rdm = new Random();
        
        //System.Timers.Timer tmr;
        [NonSerialized]
        System.Windows.Forms.Timer timer;
        //constructors
        /// <summary>
        /// This is the constructor of Crossing of type 1.
        /// </summary>
        /// <param name="p">This is the position of the crossing on the map.</param>
        /// <param name="index">This is the position of the crossing in the list (myCrossings.Count)</param>
        public CrossingType1(Point p, int i, PictureBox pbx)
        {
            Pos = p;
            im = im = Rsrc_Images.Type1_SMALL;

            Random rmd = new Random();
            indexGreen = rmd.Next(0, 4);

            myNeighbours = new List<Crossing>();
            pbox = pbx;
            index = i;
            run = false;
            crosType = Parent.Type1;

            timer = new System.Windows.Forms.Timer();
            timer.Tick += new EventHandler(timer_Tick);
            timer.Interval = 70;
            intervalToRun = 4000;


            int width = im.Width;
            int height = im.Height;
            outgoingLanes = new List<Lane>();
            blackboxLanes = new List<Lane>();

            myIncomingLanes = new LinkedList<Lane>[4];
            myIncomingLanes[0] = new LinkedList<Lane>();
            myIncomingLanes[1] = new LinkedList<Lane>();
            myIncomingLanes[2] = new LinkedList<Lane>();
            myIncomingLanes[3] = new LinkedList<Lane>();

            myIncomingLanes[0].AddLast(new Lane("N1", new Point(105, 0), new Point(110, 80), Direction.W, Parent.Type1, Rsrc_Images.car_20x20_S));
            myIncomingLanes[0].AddLast(new Lane("N2", new Point(140, 0), new Point(150, 80), Direction.S, Parent.Type1, Rsrc_Images.car_20x20_S));
            myIncomingLanes[1].AddLast(new Lane("S2", new Point(145, 333), new Point(145, 233), Direction.N, Parent.Type1, Rsrc_Images.car_20x20_N));
            myIncomingLanes[1].AddLast(new Lane("S3", new Point(185, 333), new Point(185, 233), Direction.N, Parent.Type1, Rsrc_Images.car_20x20_N));
            myIncomingLanes[2].AddLast(new Lane("W2", new Point(0, 165), new Point(75, 165), Direction.NE, Parent.Type1, Rsrc_Images.car_20x20_E));
            myIncomingLanes[2].AddLast(new Lane("W3", new Point(0, 210), new Point(75, 210), Direction.SE, Parent.Type1, Rsrc_Images.car_20x20_E));
            myIncomingLanes[3].AddLast(new Lane("E1", new Point(303, 115), new Point(210, 115), Direction.NE, Parent.Type1, Rsrc_Images.car_20x20_W));
            myIncomingLanes[3].AddLast(new Lane("E2", new Point(303, 155),new Point(210, 155), Direction.WS, Parent.Type1, Rsrc_Images.car_20x20_W));

            blackboxLanes.Add(new Lane("N1-W1", new Point(105, 80), new Point(95, 125), Direction.NE, Parent.Type1, Rsrc_Images.car_20x20_ES));
            blackboxLanes.Add(new Lane("N2-E3", new Point(140, 80), new Point(210, 200), Direction.SW, Parent.Type1, Rsrc_Images.car_20x20_WS));
            blackboxLanes.Add(new Lane("N2-S1", new Point(150, 80), new Point(105, 233), Direction.WN, Parent.Type1, Rsrc_Images.car_20x20_S));         
            blackboxLanes.Add(new Lane("S2-W1", new Point(155, 233), new Point(95, 125), Direction.WN, Parent.Type1, Rsrc_Images.car_20x20_SW));
            blackboxLanes.Add(new Lane("S2-N3", new Point(145, 233), new Point(190, 100), Direction.NE, Parent.Type1, Rsrc_Images.car_20x20_N));
            blackboxLanes.Add(new Lane("S3-E3", new Point(185, 233), new Point(210, 200), Direction.ES, Parent.Type1, Rsrc_Images.car_20x20_SE));
            blackboxLanes.Add(new Lane("W2-N3", new Point(75, 165), new Point(190, 100), Direction.NE, Parent.Type1, Rsrc_Images.car_20x20_SE));
            blackboxLanes.Add(new Lane("W2-E3", new Point(75, 165), new Point(210, 200), Direction.ES, Parent.Type1, Rsrc_Images.car_20x20_E));
            blackboxLanes.Add(new Lane("W3-S1", new Point(75, 210), new Point(105, 233), Direction.SW, Parent.Type1, Rsrc_Images.car_20x20_WS));
            blackboxLanes.Add(new Lane("E1-N3", new Point(210, 115), new Point(190, 100), Direction.NE, Parent.Type1, Rsrc_Images.car_20x20_SW));
            blackboxLanes.Add(new Lane("E2-W1", new Point(210, 155), new Point(95, 125), Direction.WN, Parent.Type1, Rsrc_Images.car_20x20_W));
            blackboxLanes.Add(new Lane("E2-S1", new Point(210, 155), new Point(105, 233), Direction.SW, Parent.Type1, Rsrc_Images.car_20x20_ES));
            

            outgoingLanes.Add(new Lane("N3", new Point(190, 80), new Point(190, 0), Direction.N, Parent.Type1, Rsrc_Images.car_20x20_N));
            outgoingLanes.Add(new Lane("S1", new Point(105, 233), new Point(105, 333), Direction.S, Parent.Type1, Rsrc_Images.car_20x20_S));
            outgoingLanes.Add(new Lane("E3", new Point(210, 200), new Point(303, 200), Direction.E, Parent.Type1, Rsrc_Images.car_20x20_E));
            outgoingLanes.Add(new Lane("W1", new Point(75, 125), new Point(0, 210), Direction.W, Parent.Type1, Rsrc_Images.car_20x20_W));
            /* We need to initialize the List of lanes regarding their position. We need to see
             * how to do that with the Lane.
             * We will name the Lanes as in the name of the crossing (e.g. C1) and the position of the 
             * lane (W, WE ...)
             * */
            

            myTrafficLight = new LinkedList<TrafficLight>[4];

            myTrafficLight[0] = new LinkedList<TrafficLight>();
            myTrafficLight[1] = new LinkedList<TrafficLight>();
            myTrafficLight[2] = new LinkedList<TrafficLight>();
            myTrafficLight[3] = new LinkedList<TrafficLight>();

            myTrafficLight[0].AddLast(new TrafficLight("N1", new Point(85, 90), Color.Red)); // N1
            myTrafficLight[0].AddLast(new TrafficLight("N2", new Point(165, 90), Color.Red)); // N2
            myTrafficLight[3].AddLast(new TrafficLight("E1", new Point(213, 90), Color.Red)); // W1
            myTrafficLight[3].AddLast(new TrafficLight("E2", new Point(210, 180), Color.Red)); // W2
            myTrafficLight[1].AddLast(new TrafficLight("S2", new Point(130, 233), Color.Red)); // S1
            myTrafficLight[1].AddLast(new TrafficLight("S3", new Point(212, 233), Color.Red)); // S2
            myTrafficLight[2].AddLast(new TLPedestrian("W2", new Point(85, 145), Color.Red)); // E1
            myTrafficLight[2].AddLast(new TLPedestrian("W3", new Point(80, 233), Color.Red)); // E2
            /* The same way we need to initialize the list of cars, with the position of the crossing. 
             * */

            foreach (Lane l in outgoingLanes)
            { l.flux = Flux.egress; l.canMove = true; }
            foreach (Lane l in blackboxLanes)
            { l.flux = Flux.InOut; l.canMove = true; }

            this.setDestinationsForLanes();

            this.pbox.Paint += new PaintEventHandler(getallCars2Draw);
        }

        //methods
        public override void RunMyPattern()
        {
            for (int i = 0; i < 4; i++)
            {
                foreach (Lane il in myIncomingLanes[i])
                {
                    // il.tmr_move.Enabled = true;
                    il.Move(null, null);
                }
            }
            foreach (Lane bl in blackboxLanes)
            {
                // bl.tmr_move.Enabled = true;
                bl.Move(null, null);
            }

            foreach (Lane ol in outgoingLanes)
            {
                // ol.tmr_move.Enabled = true;
                ol.Move(null, null);
            }

            if (first)
            {
                if (indexGreen == 0)
                {
                    indexRed = 3;
                }
                else
                {
                    indexRed = indexGreen - 1;
                }
                ChangeColors(indexGreen, indexRed);
                first = false;

            }
                // When you load the class from a file the timer is null
                if (timer == null)
                {
                    timer = new System.Windows.Forms.Timer();
                    timer.Tick += new EventHandler(timer_Tick);
                    timer.Interval = 70;
                }
            if (!timer.Enabled)
                timer.Start();

            this.pbox.Invalidate();
           
        }
        // returns index of the lane
        private int getIndexLane(string lane)
        {
            if (lane.Length == 2)
            {
                for (int i = 0; i < outgoingLanes.Count; i++)
                {
                    if (outgoingLanes[i].LaneId == lane)
                    {
                        return i;
                    }
                }
            }
            else
            {
                for (int j = 0; j < blackboxLanes.Count; j++)
                {
                    if (blackboxLanes[j].LaneId == lane)
                    {
                        return j;
                    }
                }
            }
            return -1;

        }
        // ?
        private void setDestinationsForLanes()
        {
            myIncomingLanes[0].ElementAt(0).addDestination(blackboxLanes[getIndexLane("N1-W1")]);
            myIncomingLanes[0].ElementAt(1).addDestination(blackboxLanes[getIndexLane("N2-E3")]);
            myIncomingLanes[0].ElementAt(1).addDestination(blackboxLanes[getIndexLane("N2-S1")]);

            myIncomingLanes[1].ElementAt(0).addDestination(blackboxLanes[getIndexLane("S2-W1")]);
            myIncomingLanes[1].ElementAt(0).addDestination(blackboxLanes[getIndexLane("S2-N3")]);
            myIncomingLanes[1].ElementAt(1).addDestination(blackboxLanes[getIndexLane("S3-E3")]);

            myIncomingLanes[2].ElementAt(0).addDestination(blackboxLanes[getIndexLane("W2-N3")]);
            myIncomingLanes[2].ElementAt(0).addDestination(blackboxLanes[getIndexLane("W2-E3")]);
            myIncomingLanes[2].ElementAt(1).addDestination(blackboxLanes[getIndexLane("W3-S1")]);

            myIncomingLanes[3].ElementAt(0).addDestination(blackboxLanes[getIndexLane("E1-N3")]);
            myIncomingLanes[3].ElementAt(1).addDestination(blackboxLanes[getIndexLane("E2-W1")]);
            myIncomingLanes[3].ElementAt(1).addDestination(blackboxLanes[getIndexLane("E2-S1")]);

            for (int i = 0; i < blackboxLanes.Count; i++)
            {
                string name = blackboxLanes[i].LaneId.Substring(3, 2);

                blackboxLanes[i].addDestination(outgoingLanes[getIndexLane(name)]);
            }
        }



        //not sure what this is for
        public override void ResetObjects()
        {
            //throw new NotImplementedException();
            this.intervalRunning = 0;

            for (int i = 0; i < 4; i++)
            {
                foreach (Lane il in myIncomingLanes[i])
                {
                    il.ResetLane();
                }
            }
            foreach (Lane bl in blackboxLanes)
            {
                bl.ResetLane();
            }

            foreach (Lane ol in outgoingLanes)
            {
                ol.ResetLane();
            }

            for (int i = 0; i < 4; i++)
            {
                foreach (TrafficLight tl in myTrafficLight[i])
                {
                    tl.color = Color.Red;
                }
            }
            this.pbox.Invalidate();
        }

        /// <summary>
        /// This function generates a car on one of the lanes. First picks a departure, then from that
        /// departure picks a destination. Finally, adds that car to the lane.
        /// </summary>
        public override void GenerateCars()
        {
            Lane departure = this.PickDeparture();
            Lane destination = departure.PickDestination();
            //int speed = 10; // I was thinking about randomly generate it 
            departure.AddCarLane(new Car(departure, destination, moveToBlackbox,null));
            /* Inside a lane, we have to have the list of possible destinations and a function 
             * to randomly pick a destination from the available ones.
             * My idea for a car is: with the departure and destination we can generate the starting 
             * position, inside the Car class we should generate the color and the speed.
             */
        }


        public override Lane PickDeparture()
        {
            bool found = false;
            int indexList = 0;
            int indexLane = 0;
            int count = 0;
            while (!found && count < 6)
            {
                indexList = rdm.Next(4);
                indexLane = rdm.Next(myIncomingLanes[indexList].Count);
                if (myIncomingLanes[indexList].ElementAt(indexLane).isFull() || myIncomingLanes[indexList].ElementAt(indexLane).HasNeighbours())
                {
                    found = false;
                }
                else
                {
                    found = true;
                }
                count++;
            }
            return found ? myIncomingLanes[indexList].ElementAt(indexLane) : null;
              
        }

        ~CrossingType1()
        { pbox.Paint -= new PaintEventHandler(this.getallCars2Draw); }

        public override void getallCars2Draw(object o,PaintEventArgs e)
        {
            // todo
            
             for (int j = 0; j < myTrafficLight.Length; j++)
            {
                foreach (TrafficLight tl in myTrafficLight[j])
                {
                    if (tl.color == Color.Green)      
                        e.Graphics.FillEllipse(Brushes.Green, tl.position.X, tl.position.Y, 10, 10);
                    else if (tl.color == Color.Red)
                        e.Graphics.FillEllipse(Brushes.Red, tl.position.X, tl.position.Y, 10, 10);
                    else
                        e.Graphics.FillEllipse(Brushes.Orange, tl.position.X, tl.position.Y, 10, 10);
                }
            }


             for (int j = 0; j < myIncomingLanes.Length; j++)
             {
                 foreach (Lane l in myIncomingLanes[j])
                 {
                     for (int i = 0; i < l.NumberOfCars; i++)
                         e.Graphics.DrawImage(l.img, l.GetCarfromList(i).position);
                 }
             }

             // Get the car Points from the BlackBoxLanes
             foreach (Lane l in blackboxLanes)
             {
                 for (int i = 0; i < l.NumberOfCars; i++)
                     e.Graphics.DrawImage(l.img, l.GetCarfromList(i).position);
             }

             // Get the car Points from the outgoingLanes
             foreach (Lane l in outgoingLanes)
             {
                 for (int i = 0; i < l.NumberOfCars; i++)
                     e.Graphics.DrawImage(l.img, l.GetCarfromList(i).position);
             }
        }

        bool changed2orange = false;
        private void timer_Tick(object sender, EventArgs e)
        {
            intervalRunning += timer.Interval;

            if (intervalRunning >= intervalToRun - 1500 && !changed2orange)
            { changeToOrange(indexGreen); }

            if (intervalRunning >= intervalToRun)
            {
                intervalRunning = 0;
                indexGreen = (indexGreen + 1) % 4;
                if (indexGreen == 0)
                {
                    indexRed = 3;
                }
                else
                {
                    indexRed = indexGreen - 1;
                }
                ChangeColors(indexGreen, indexRed);


            }
            RunMyPattern();
            pbox.Refresh();
        }

        private void ChangeColors(int indexGreen, int indexRed)
        {
            intervalToRun = myTrafficLight[indexGreen].ElementAt(0).intervalTobeGreen;

            foreach (TrafficLight t in myTrafficLight[indexRed])
            {
                t.color = Color.Red;
            }

            foreach (Lane l in myIncomingLanes[indexRed])
            {
                l.canMove = false;
            }

            foreach (TrafficLight t in myTrafficLight[indexGreen])
            {
                t.color = Color.Green;
            }

            foreach (Lane l in myIncomingLanes[indexGreen])
            {
                l.canMove = true;
            }
            changed2orange = false;
        }

        private void changeToOrange(int indexGreen)
        {
            changed2orange = true;
            foreach (TrafficLight t in myTrafficLight[indexGreen])
            {
                t.color = Color.Orange;
            }

            foreach (Lane l in myIncomingLanes[indexGreen])
            {
                l.canMove = false;
            }

            for (int i = 0; i < blackboxLanes.Count; i++)
            {
                for (int c = 0; c < blackboxLanes[i].NumberOfCars; c++)
                    blackboxLanes[i].GetCarList()[c].speed = 5;
            }
        }

//        public override void AddNeighbour(Crossing c)
//        {

                /*
                 * 
                 * The idea is that we can have the index of the crossing, fixed!
                 * Since the crossing have fixed pictureboxes, we can take the number 
                 * of the picturebox holding the crossing and use it as the index to find
                 * if we have neighbours.
                    
                    
//            this.myNeighbours.Add(c);
//            if (this.Pos.X < c.Pos.X && this.Pos.Y == c.Pos.Y)
//            {
//                /* It means that the new crossing is in the West, so we need to connect the 
//                 * West lanes of this crossing with the East lanes of c
//                 * */
//            }
//            else
//            {
//                if (this.Pos.X > c.Pos.X && this.Pos.Y == c.Pos.Y)
//                {
//                    /* It means that the new crossing is in the East, so we need to connect the
//                     * East lanes of this crossing with the West lanes of c
//                     * */
//                }
//                else
//                {
//                    if (this.Pos.X == c.Pos.X && this.Pos.Y < c.Pos.Y)
//                    {
//                        /* It means that the new crossing is in the North, so we need to connect the
//                         * North lanes of this crossing with the South lanes of c
//                         * */
//                    }
//                    else
//                    {
//                        if (this.Pos.X == c.Pos.X && this.Pos.Y > c.Pos.Y)
//                        {
//                            /* It means that the new crossing is in the South, so we need to connect the
//                            * South lanes of this crossing with the North lanes of c
//                            * */
//                        }
//                    }
//                }
//            }
//        }

        /// <summary>
        /// It takes the moving object and removes it from the blackbox lane, adding to the outgoing lane.
        /// </summary>
        /// <param name="mo">The moving object to be moved.</param>
        public override void moveToLane(MovingObject mo)
        {
            Lane newLane = mo.desti;
            Lane previousLane = mo.depart;
            Lane newDestination = newLane.PickDestination();

            if (!newLane.isFull())
            {
                newLane.AddCarLane(new Car(newLane, newDestination, null, changeToCrossing));                
                previousLane.RemoveCarLaneStart();
            }
            else
                return;
        }


        public override void changeToCrossing(MovingObject mo)
        {
            // if the lane in the other crossing is full, do nothing.
            // if there is no crossing connected, delete the Car.
            // If lane exist and is not full...
            // Calls the add car function of the other Crossing, It doesn't pass the car..

            if (mo.desti != null)
            {
                Lane previousLane = mo.depart;
                Lane newLane = mo.desti;
                Lane newDestination = newLane.PickDestination();
                if (!newLane.isFull())
                {
                    newLane.AddCarLane(new Car(newLane, newDestination, moveToBlackbox, null));
                    previousLane.RemoveCarLaneStart();
                }
                else
                    return;
            }
            else
            {
                mo.depart.RemoveCarLaneStart();
            }
        }

        /// <summary>
        /// It takes the moving object and removes it from the ingoing lane, adding to the blackbox lane.
        /// </summary>
        /// <param name="mo"></param>
        public override void moveToBlackbox(MovingObject mo)
        {
            ///If the light is green and the lane to move isn't full
            ///change lane. otherwise do nothing
            Lane newLane = mo.desti;
            Lane previousLane = mo.depart;
            Lane newDestination = newLane.PickDestination();

            if (!newLane.isFull())
            {
                newLane.AddCarLane(new Car(newLane, newDestination, moveToLane, null));
                int index = newLane.GetCarList().Count - 1;
                newLane.GetCarList()[index].isOnBlckbx = true;
                newLane.GetCarList()[index].speed = 3;// changes to true, it helps with the move functionality
                previousLane.RemoveCarLaneStart();
            }
            else
                return;
        }

        public override void AddNeighbour(ref Crossing c)
        {
            if (c.getIndex() == this.index + 1) // on the right side
            {
                // Direction: West TO EAST
                // E3 Lane gets a west side neighbour
                List<Lane> psbl = c.getPossibleLanes(Direction.E);

                for (int i = 0; i < psbl.Count; i++)
                {
                    // Tell the incoming lane that a neighbour is present
                    psbl[i].addNeighbour(null);
                    this.outgoingLanes[getIndexLane("E3")].addNeighbour(psbl[i]);
                }
            }
            else if (c.getIndex() == this.index + 3) //  underneath you
            {
                // Direction: South TO North
                // S1 Lane gets a north side neigbour
                List<Lane> psbl = c.getPossibleLanes(Direction.S);
                int j = getIndexLane("S1");
                for (int i = 0; i < psbl.Count; i++)
                {
                    // Tell the incoming lane that a neighbour is present
                    psbl[i].addNeighbour(null);
                    this.outgoingLanes[getIndexLane("S1")].addNeighbour(psbl[i]);
                }
            }
            else
                return;

            // Tell to your neighbour to add you
            c.GetNeighbour((Crossing)this);
        }
        public override void GetNeighbour(Crossing c)
        {
            if (c.getIndex() == this.index - 1) // on the left side
            {
                // Direction East TO West
                // W1 gets an East side neighbour
                List<Lane> psbl = c.getPossibleLanes(Direction.W);
                int j = getIndexLane("W1");
                for (int i = 0; i < psbl.Count; i++)
                {
                    psbl[i].addNeighbour(null);
                    this.outgoingLanes[j].addNeighbour(psbl[i]);
                }
            }
            else if (c.getIndex() == this.index - 3) // above you
            {
                // Direction North TO South
                // N3 gets a South Neighbour
                List<Lane> psbl = c.getPossibleLanes(Direction.N);

                for (int i = 0; i < psbl.Count; i++)
                {
                    psbl[i].addNeighbour(null);
                    this.outgoingLanes[getIndexLane("N3")].addNeighbour(psbl[i]);
                }
            }
            else
                return;
        }
        public override List<Lane> getPossibleLanes(Direction dir)
        {
            List<Lane> toReturn = new List<Lane>();
            switch (dir)
            {
                case Direction.N:
                    toReturn = this.myIncomingLanes[1].ToList();
                    break;
                case Direction.E:
                    toReturn = this.myIncomingLanes[2].ToList();
                    break;
                case Direction.S:
                    toReturn = this.myIncomingLanes[0].ToList();
                    break;
                case Direction.W:
                    toReturn = this.myIncomingLanes[3].ToList();
                    break;
            }
            return toReturn;
        }

        public override void StopRunning()
        {
            timer.Stop();
            this.pbox.Invalidate();
        }

        public override List<Pedestrian> getPedestrians()
        {
            return null;
        }

        public override List<PedLane> getPedestrianLanes()
        { return null; }
    }
}
